using System;

namespace Dida
{
	[Dxml.DidaObject]
	public class PerlinFloat : IAniValue<float>
	{
		Octave[] octaves;
		float curValue;

		[Dxml.AutoXmlConstructor]
		public PerlinFloat(int nOctaves,float min,float max,float baseFreq)
		{
			float mid = (min + max) * .5f;
			float expand = (max - min) * .5f;

			octaves = new Octave[nOctaves];
			for (int i = 0; i < nOctaves; i++)
			{
				octaves[i] = new Octave(mid - expand, mid + expand,baseFreq);
				baseFreq *= 2;
				expand *= .5f;
			}
		}

		public void Start()
		{
			Update(0);
		}

		public void Update(float timeDelta)
		{
			curValue = 0;
			for (int i = 0; i < octaves.Length; i++)
			{
				octaves[i].Update(timeDelta);
				curValue += octaves[i].CurValue;
			}
		}

		public float CurrentValue
		{
			get { return curValue; }
		}

		private class Octave
		{
			SmoothedNoise noise;
			float prev;
			float next;
			float t;
			float speed;

			float cur;

			public Octave(float min, float max, float speed)
			{
				noise = new SmoothedNoise(min, max);
				prev = noise.Next();
				next = noise.Next();
				t = 0;
				this.speed = speed;
			}

			public void Update(float timeDelta)
			{
				t += speed;
				if (t < 1)
				{
					cur = prev + (next - prev) * t;
				}
				else if(t < 2)
				{
					prev = next;
					next = noise.Next();
					t--;
					cur = prev + (next - prev) * t;
				}
				else
				{
					prev = noise.Next();
					next = noise.Next();
					t -= (float)Math.Floor(t);
					cur = prev + (next - prev) * t;
				}
			}

			public float CurValue
			{
				get { return cur; }
			}
		}
	}

	internal struct SmoothedNoise
	{
		float[] vals;

		int writePtr;
		Random rnd;
		float min;
		float range;

		public SmoothedNoise(float min,float max)
		{
			vals = new float[3];

			writePtr = 0;
			rnd = new Random();
			this.min = min;
			range = max - min;

			vals[0] = (float)rnd.NextDouble() * range + min;
			vals[1] = (float)rnd.NextDouble() * range + min;
			vals[2] = (float)rnd.NextDouble() * range + min;
		}

		public float Next()
		{
			vals[writePtr] = (float)rnd.NextDouble() * range + min;
			writePtr = (writePtr + 1) % 3;

			return (vals[0] + vals[1] + vals[2]) * .3f;
		}
	}
}